admin / Strike
publicWeb-Based UK Cyber Compliance Tool with Reporting
Strike / StrikeXi v3 / backend / app / main.py
3226 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | import time from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import text from sqlalchemy.exc import OperationalError from .config import settings from .database import engine, SessionLocal, Base from . import models from .security import hash_password, verify_password from .routers import auth, catalogue, assessments, audit, users, frameworks app = FastAPI(title="StrikeXi API", version="3.0.0", description="Multi-framework Cyber Maturity Assessment platform " "(NCSC CAF, UK Cyber Security & Resilience Bill)") app.add_middleware( CORSMiddleware, allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(",")], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth.router) app.include_router(users.router) app.include_router(frameworks.router) app.include_router(catalogue.router) app.include_router(assessments.router) app.include_router(audit.router) def _wait_for_db(retries: int = 30): for i in range(retries): try: with engine.connect() as conn: conn.execute(text("SELECT 1")) return except OperationalError: time.sleep(2) raise RuntimeError("Database not reachable after retries") @app.on_event("startup") def on_startup(): _wait_for_db() # Schema is created by SQL init scripts; ensure ORM tables exist too (idempotent). Base.metadata.create_all(bind=engine) # Seed / self-heal the admin user (bcrypt). db = SessionLocal() try: existing = db.query(models.User).filter( models.User.username == settings.ADMIN_USERNAME).first() if not existing: # First boot on a fresh database: create the admin and force a real # password to be set on first login. db.add(models.User( username=settings.ADMIN_USERNAME, password_hash=hash_password(settings.ADMIN_PASSWORD), full_name="Administrator", role="admin", is_active=True, must_change_password=True, )) db.commit() else: # Self-heal the first-login admin setup. The DB volume persists across # rebuilds, so the admin isn't re-seeded — but if it STILL uses the # default configured password it was never personalised. Re-arm the # forced password change so the first-login admin setup reappears. # An admin who has already set a real password is left untouched. still_default = verify_password(settings.ADMIN_PASSWORD, existing.password_hash) changed = False if still_default and not existing.must_change_password: existing.must_change_password = True changed = True # Never leave the only administrator disabled/locked out of setup. if still_default and not existing.is_active: existing.is_active = True changed = True if changed: db.commit() finally: db.close() @app.get("/api/health") def health(): return {"status": "ok"} |